行为变更 | Android 12 中不受信任的触摸事件
触摸控制是 Android 系统中同应用进行交互的主要方式。Android 12 中采取了额外的措施,来确保触摸事件被正确地传递给了应该响应此事件的应用,以此确保触摸交互的直观和安全性。具体地说,就是在 Android 12 中,如果触摸事件是从一个不同的应用窗口传递的,那么此事件会被屏蔽。这一改变将影响所有在 Android 12 中运行的应用,且与应用的 "targetSdkVersion" 无关。这有助于帮助用户更直观地确认他们正在进行交互的应用。继续阅读本文来看看您的应用是否会受到此变更的影响,以及了解如何针对此变更测试您的应用。
尽可能使用特殊用途 (Special-Purpose) 的 API
在检查您的应用是否会受此影响之前,最好先评估一下您应用的某些场景是否可以使用以下 Android API。这些 API 部分由系统控制,可以被信任,因此您不需要担心使用这些 API 会受到限制。它们是:
Bubbles: Bubbles 是像气泡一样浮动在其他应用内容的上层,并且会跟随用户所处的位置,还可以展开以显示应用功能和信息。
Picture-in-Picture (PIP): PIP 功能允许应用在一个小窗口中显示内容,用户可以通过固定到屏幕一角的小窗口观看视频,同时在应用之间进行导航或浏览主屏幕上的内容。用户还可以拖动 PIP 窗口,并可以点击它来展开或进行关闭。
Notifications: 通知是指 Android 在您应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或您应用中的其他适时弹出的信息。用户可以点按通知来打开应用,或直接在通知中执行操作。
Snackbars 和 Toasts: 如果您需要在应用中显示一条简短的信息,可以使用 Snackbars。如果您需要应用在后台的情况下显示信息,可以看看 Toasts 是否符合您的需求。
Bubbles
https://developer.android.google.cn/guide/topics/ui/bubblesPicture-in-Picture
https://developer.android.google.cn/guide/topics/ui/picture-in-pictureNotifications
https://developer.android.google.cn/guide/topics/ui/notifiers/notificationsSnackbars
https://developer.android.google.cn/reference/com/google/android/material/snackbar/SnackbarToasts
https://developer.android.google.cn/guide/topics/ui/notifiers/toasts
我会受到影响吗?
使用 FLAG_NOT_TOUCHABLE 标志的 TYPE_APPLICATION_OVERLAY 窗口。 使用 FLAG_NOT_TOUCHABLE 标志的 Activity 窗口。
FLAG_NOT_TOUCHABLE
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#FLAG_NOT_TOUCHABLETYPE_APPLICATION_OVERLAY
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams?hl=zh-TW#TYPE_APPLICATION_OVERLAY
应用中的交互。只有当用户与您的应用进行互动时才会显示叠加层。
可信窗口。这些窗口包括但不仅限于: 无障碍窗口、输入法 (IME) 窗口和 Google 助理窗口。
不可见窗口。窗口根视图是 GONE 或 INVISIBLE。
全透明窗口。窗口的 alpha 属性值为 0.0。
足够半透明的系统警报窗口。该窗口是 TYPE_APPLICATION_OVERLAY 类型的,并且其 alpha 值等于或小于 InputManager.getMaximumObscuringOpacityForTouch() 的值。这个值目前在开发者预览 3 的版本中是 0.8,但在最终版发布之前可能会改变。如果有多个这种类型的重叠窗口,则会使用它们各自的 opacity 值合并计算出一个值作为最终的结果。
无障碍窗口
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams?hl=zh-TW#TYPE_ACCESSIBILITY_OVERLAY输入法 (IME) 窗口
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams?hl=zh-TW#TYPE_INPUT_METHODGoogle 助理窗口
https://developer.android.google.cn/reference/android/service/voice/VoiceInteractionSessionGONE
https://developer.android.google.cn/reference/android/view/View#GONEINVISIBLE
https://developer.android.google.cn/reference/android/view/View#INVISIBLEalpha
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#alphaInputManager.getMaximumObscuringOpacityForTouch() https://developer.android.google.cn/reference/android/hardware/input/InputManager#getMaximumObscuringOpacityForTouch() 合并计算出一个值
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#combined-obscuring-opacity
有着透明背景且无 UI 元素的窗口
在有着透明背景的窗口中展示某些 UI 的应用,可以在适当的时候在视图层面隐藏它们的 UI,同时添加 FLAG_NOT_TOUCHABLE 标志,这样用户就可以与下层的内容进行交互了。
使用 WindowManager.removeView() 来移除窗口,并传递给根视图。
通过在根视图上调用 View.setVisibility(),使用 View.GONE 或 View.INVISIBLE 让窗口不可见。
通过 LayoutParams.alpha 让窗口的 opacity 值为 0.0。
WindowManager.removeView()
https://developer.android.google.cn/reference/android/view/ViewManager#removeView(android.view.View)View.setVisibility()
https://developer.android.google.cn/reference/android/view/View#setVisibility(int)View.GONE
https://developer.android.google.cn/reference/android/view/View#GONEView.INVISIBLE
https://developer.android.google.cn/reference/android/view/View#INVISIBLELayoutParams.alpha
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#alpha
每当您需要再次显示那个界面时,您只需要把上面的动作反过来设置即可。
不必要的大窗口
有时应用会想要显示一些小的 UI 界面,并同时允许用户同窗口下层的内容进行交互。在之前,应用可以通过简单地使用一个全屏窗口,并将其标记为 FLAG_NOT_TOUCHABLE 来实现,如图 1 所示:
请注意,在以前的操作系统版本中,通过实际的 UI 元素进行的触摸事件,在这种情况下会传递到下层的窗口中。在这种情况下,首先请查看一下 Toast API 是否能满足需求,不行的话,解决方案也很简单——在右侧的图片有说明: 您只需要将窗口的边界缩小到实际的 UI 大小,并使用 FLAG_NOT_TOUCH_MODAL,有时您也可能需要删除 FLAG_NOT_TOUCHABLE。
Toast
https://developer.android.google.cn/guide/topics/ui/notifiers/toastsFLAG_NOT_TOUCH_MODAL
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL
这样,在您的 UI 外的触摸事件,将直接穿透到下层的窗口,不再被屏蔽。
半透明窗口
如果您使用了一个 TYPE_APPLICATION_OVERLAY 窗口,并需要在显示内容时允许触摸事件穿透下去,那您就必须降低窗口的不透明度,让用户能够合理地看到他们在窗口后所触摸的 UI 元素。
LayoutParams.alpha
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#alphaInputManager.getMaximumObscuringOpacityForTouch()
https://developer.android.google.cn/reference/android/hardware/input/InputManager#getMaximumObscuringOpacityForTouch()FLAG_NOT_TOUCHABLE
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE
无障碍服务
TYPE_ACCESSIBILITY_OVERLAY
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams#TYPE_ACCESSIBILITY_OVERLAYAccessibilityService
https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityServiceWindowManager
https://developer.android.google.cn/reference/android/view/WindowManager
如何测试您的应用是否受到影响
如果系统屏蔽了触摸操作,Logcat 会输出以下信息:
Untrusted touch due to occlusion by PACKAGE_NAME.
Logcat
https://developer.android.google.cn/studio/command-line/logcat?hl=zh-TW
接下来
如果您想了解到更多关于不受信任的触摸事件以及它们的例外情况,请查阅相关文档:
不受信任的触摸事件
https://developer.android.google.cn/about/versions/12/behavior-changes-all?hl=zh-TW#untrusted-touch-events
例外情况
https://developer.android.google.cn/about/versions/12/behavior-changes-all?hl=zh-TW#untrusted-touch-events-exceptions
推荐阅读